package eu.webhib.frmwrk.jsf.base;

import eu.webhib.frmwrk.jsf.exception.FatalApplException;
import eu.webhib.frmwrk.jsf.utils.MsgUtil;
import eu.webhib.frmwrk.persistence.PersistenceMngr;
import javax.faces.application.FacesMessage;
import javax.faces.context.FacesContext;
import javax.faces.event.ComponentSystemEvent;
import org.apache.log4j.Logger;

/**
 * Provides base functionalities for bean controllers
 */
public class EditBaseController {

   private static final Logger log = Logger.getLogger(EditBaseController.class);
   /** Used with Composite Components */
   private static final String INPUT_CHILD_ID = ":input";
   /** The page or dialog global message text associated with the controller bean.
    *  Provides the same live time scope the bean itself has (e.g. session scope not just only request scope) */
   private String message;

   /** The severity of the message text, either INFO, CONFIRM, ERROR or FATAL */
   private enum Severity {

      INFO, CONFIRM, ERROR, FATAL
   };
   Severity severity;

   /**
    * Wrapper/Closure for catching exceptions from method invocations called
    * directly or indirectly from the GUI view.
    * The wrapped method has database access thus database exceptions are handled
    * and mapped to user error messages.
    */
   protected abstract class EditActionClosure {

      /** has to be implemented by the client */
      abstract protected void run();

      public String start() {
         try {
            run();
            PersistenceMngr.commitTransaction();
         } catch (javax.persistence.OptimisticLockException t) {
            log.info("EditActionClosure - OptimisticLock");
            setErrorMessage("error_entity_changed");
            PersistenceMngr.rollbackTransaction();
         } catch (javax.persistence.PersistenceException t) {
            log.info("EditActionClosure - Persistence");
            setErrorMessage("error_entity_related");
            PersistenceMngr.rollbackTransaction();
         }
         return null;
      }
   };

   /**
    * Wrapper/Closure for catching exceptions from method invocations called
    * directly or indirectly from the GUI view.
    * The wrapped method has database access thus database exceptions are handled
    * and mapped to user error messages.
    * In contrast to the EditActionClosure this closure is suitable for listener
    * methods providing an event parameter.
    */
   protected abstract class EditListenerClosure {

      /** has to be implemented by the client */
      abstract protected void run(ComponentSystemEvent event);

      public void start(ComponentSystemEvent event) {
         try {
            run(event);
            PersistenceMngr.commitTransaction();
         } catch (javax.persistence.OptimisticLockException t) {
            log.info("EditListenerClosure - OptimisticLock");
            setErrorMessage("error_entity_changed");
            PersistenceMngr.rollbackTransaction();
         } catch (javax.persistence.PersistenceException t) {
            log.info("EditListenerClosure - Persistence");
            setErrorMessage("error_entity_related");
            PersistenceMngr.rollbackTransaction();
         } 
      }
   };

   /**
    * Get the message text associated with this controller bean
    */
   public String getMessageText() {
      return message;
   }

   /**
    * Return CSS style class depending on the severity of the current page global message
    * @return CSS style class name associated to the message severity
    */
   public String getMessageStyleClass() {
      if (severity == null) {
         return null;
      }
      switch (severity) {
         case INFO:
            return "page-message-info";
         case CONFIRM:
            return "page-message-confirm";
         case ERROR:
            return "page-message-error";
         case FATAL:
            return "page-message-fatal";
         default:
            assert false;
            return null;
      }
   }

   /**
    * Return header titles depending on the severity of the current page global message,
    * e.g used by a dialog window
    * @return header title string
    */
   public String getMessageHeader() {
      if (severity == null) {
         return null;
      }
      switch (severity) {
         case INFO:
            return MsgUtil.getMessageText("title_info_message");
         case CONFIRM:
            return MsgUtil.getMessageText("title_confirm_message");
         case ERROR:
            return MsgUtil.getMessageText("title_error_message");
         case FATAL:
            return MsgUtil.getMessageText("title_fatal_message");
         default:
            assert false;
            return null;
      }
   }

   /**
    * Set the page or dialog global message with info severity
    * @param msgKey key of message loaded from resource bundle
    * @param args arguments used for parameter substitution in retrieved message text
    */
   public void setInfoMessage(String msgKey, Object... args) {
      message = MsgUtil.getMessageText(msgKey, args);
      severity = Severity.INFO;
   }

   /**
    * Set the page or dialog global message with confirmation/warning severity
    * @param msgKey key of message loaded from resource bundle
    * @param args arguments used for parameter substitution in retrieved message text
    */
   public void setConfirmMessage(String msgKey, Object... args) {
      message = MsgUtil.getMessageText(msgKey, args);
      severity = Severity.CONFIRM;
   }

   /**
    * Set the page or dialog global message with error severity
    * @param msgKey key of message loaded from resource bundle
    * @param args arguments used for parameter substitution in retrieved message text
    */
   public void setErrorMessage(String msgKey, Object... args) {
      message = MsgUtil.getMessageText(msgKey, args);
      severity = Severity.ERROR;
      FacesContext.getCurrentInstance().validationFailed();
   }

   /**
    * Set the page or dialog global message with fatal severity
    * @param msgKey key of message loaded from resource bundle
    * @param args arguments used for parameter substitution in retrieved message text
    */
   public void setFatalMessage(String msgKey, Object... args) {
      message = MsgUtil.getMessageText("fatal_message_prefix") + " " + MsgUtil.getMessageText(msgKey, args);
      severity = Severity.FATAL;
      FacesContext.getCurrentInstance().validationFailed();
   }

   /**
    * Add a further text to the current page/dialog global message
    * @param msgKey key of message loaded from resource bundle
    * @param args arguments used for parameter substitution in retrieved message text
    */
   public void appendMessageText(String msgKey, Object... args) {
      message = message + " " + MsgUtil.getMessageText(msgKey, args);
   }

   /**
    * Unset global message
    */
   public void clearMessage() {
      message = null;
      severity = null;
   }

   /**
    * Set the field specific error message and set the error flag, see isInputOk().
    * This message has request scope live time.
    * @param fieldId field (JSF field id) the error message is assigned to
    * @param msgKey key of message loaded from resource bundle
    * @param args arguments used for parameter substitution in retrieved message text
    */
   public static void setFieldError(String fieldId, String msgKey, Object... args) {
      /** fieldId: the id of the input-tag, if
       * <h:form id="register-form">
       *   <h:inputText id="email"
       *   -> fieldId = "register-form:email"
       * <h:form id="register-form" prependId="false">
       *   <h:inputText id="email"
       *   -> fieldId = "email"
       * When composite components are used, the composite component carries the given fieldId
       * and the input-tag is a children with the id="input", so the fieldId is then "email:input"
       */
      /** set validation error flag */
      FacesContext.getCurrentInstance().validationFailed();
      FacesContext.getCurrentInstance().addMessage(fieldId + INPUT_CHILD_ID,
              MsgUtil.createFacesMessage(FacesMessage.SEVERITY_ERROR, msgKey, args));
   }

   /**
    * Set the field specific error message and set the error flag, see isInputOk().
    * The field name is used as the first parameter of the message parameter substitution.
    * This message has request scope live time.
    * @param fieldId field (JSF field id) the error message is assigned to
    * @param msgKey key of message loaded from resource bundle
    * @param args the other arguments used for message parameter substitution
    */
   public static void setFieldErrorFieldName(String fieldId, String msgKey, Object... args) {
      Object msgArgs[] = new Object[args.length + 1];
      msgArgs[0] = MsgUtil.getMessageText("field_" + fieldId);
      int i = 1;
      for (Object o : args) {
         msgArgs[i++] = o;
      }
      setFieldError(fieldId, msgKey, msgArgs);
   }

   /**
    * If any error message is assigned to a field or a global error or fatal message is set within the current request scope
    * @see setFieldError, setFieldErrorFieldName
    */
   public static boolean isInputOk() {
      return !FacesContext.getCurrentInstance().isValidationFailed();
   }
}
